package com.example.dy.pay.controller;

import com.example.dy.pay.controller.thirdparty.Ali;
import com.example.dy.pay.domain.PayResult;
import com.example.dy.pay.domain.PaymentInfo;
import com.example.dy.pay.service.BasePayService;
import com.example.dy.pay.service.PayResultService;
import com.example.dy.pay.service.PaymentInfoService;
import com.example.dy.pay.service.feign.ProductTradeSgOss;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.dayu521.OrRes;
import org.example.dayu521.OrderState;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;

//pay
//        请求付款链接 -> 已付款 -> 付款成功通知
//        注意,处于用户请求付款链接与pay服务收到付款通知之间的状态时,我们并不能知道用户是否处于付款状态
//        order
//        订单超时处理 ->
//        1 确定订单状态.
//        发送消息关闭剩余pay链接,他们收到消息后进行退款
//        主动请求关闭 ->
//        1 如果用户没有请求付款链接,则pay变成canceled阶段,处理完成
//        2 如果用户在请求付款链接与付款之间,首先pay变成cancel,然后关闭pay的已经请求付款链接的订单,pay变为canceled
//        3 如果用户处于付款与付款成功通知之间,不更改pay状态.关闭其他订单
//        4 如果处于付款成功通知之后,则关闭失败,不更改pay状态

//  付款 付款成功 是两个事件
//      但是付款动作由三方服务完成,所以只需要处理付款成功事件
//  同理
//  关闭 关闭成功
//      但是关闭是由我们自身实现,所以 我们在函数中要同时处理这两种情况
//  因此从根本上说所有动作都是两个操作组成的

@RestController
//@RequestMapping("/forward")
@CrossOrigin
@Slf4j
public class Pay {

    @Autowired
    private PaymentInfoService paymentInfoService;

    @Autowired
    private PayResultService payResultService;

    @Autowired
    public void setAli(Ali ali) {
        this.ali = ali;
    }

    private Ali ali;

    @Autowired
    private BasePayService basePayServiceComposite;

    @Autowired
    private ProductTradeSgOss productTradeSgOss;

    @PostMapping("/pay")
    public OrRes<?> pay(@RequestBody Long order_id) {
        //更新失败,但不说明更新原因
        //todo 数据库需要一个完成时间字段
        var ok = paymentInfoService.lambdaUpdate().
                eq(PaymentInfo::getOrder_no, order_id).
                eq(PaymentInfo::getTrade_state, "0").
                set(PaymentInfo::getTrade_state, "1").
                set(PaymentInfo::getUpdate_time, new Date()).
                update();
        if (!ok) {
            PaymentInfo one = paymentInfoService.lambdaQuery().
                    eq(PaymentInfo::getOrder_no, order_id).one();
            if (one == null) {
                return OrRes.error("没有此订单");
            } else if (one.getTrade_state().equals("2")) {
                return OrRes.error("已退款");
            } else if (one.getTrade_state().equals("3")) {
                return OrRes.error("交易被关闭");
            } else {
                //todo zhi fu cheng gong biao
            }
        }
        //todo 更新sg-oss服务的订单表的状态,失败也不用管,sg-oss服务进行定时回查
        return OrRes.ok();
    }

    public static OrderState get_3p_order_state(PaymentInfo paymentInfo) {
        return switch (paymentInfo) {
            case null -> OrderState.NotFound;
            case PaymentInfo p when p.getTrade_state().equals("3") -> OrderState.Canceled;
            case PaymentInfo p when p.getTrade_state().equals("2") -> OrderState.Refund;
            case PaymentInfo p when p.getTrade_state().equals("1") -> OrderState.Payed;
            case PaymentInfo p when p.getTrade_state().equals("0") -> OrderState.Created;
            default -> throw new RuntimeException("未实现");
        };
    }

    private static final Map<OrderState, String> pi_order_state_map = Map.of(
            OrderState.Created, "0",
            OrderState.NotFound, "0",
            OrderState.Payed, "1",
            OrderState.Refund, "2",
            OrderState.Canceled, "3"
    );

    //支付结果通知
    @PostMapping("/pay_listen/{svc}")
    public Object pay_listen(@RequestParam Map<String, String> p, @PathVariable(required = true) String svc) {
        //处理通知
        var res = basePayServiceComposite.pay(p, svc);
        //插入失败时,说明当前已经进入下一个状态,例如,退款
        if (res.getPaymentInfo() == null) {
            return res.getReply();
        }
        if(!try_save_pay(res)) {
            var paymentInfo = res.getPaymentInfo();
            streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(paymentInfo.getOrder_no(), paymentInfo.getPayment_type()));
            return res.getReply();
        }
        //失败不用管.订单超时会回查.finish_pay是幂等的
        productTradeSgOss.finish_pay(new ProductTradeSgOss.FinishTrade(res.getPaymentInfo().getOrder_no(), OrderState.Payed.getValue()));
        return res.getReply();
    }

    private boolean try_save_pay(BasePayService.PayAck<?> res) {

        //前幂等
        PayResult one = payResultService.lambdaQuery().eq(PayResult::getOrder_no, res.getPaymentInfo().getOrder_no()).one();
        OrderState orderState = payResultState(one);

        if (one==null) {
            ;
        }else if(one.getPayinfo_id()== res.getPaymentInfo().getId()){//过时的付款通知.
            return true;
        }else if(one.getPayinfo_id()>0l){//其他三方服务支付了
            return false;
        }else{
            log.warn(STR."为什么到达这里 \{res.getPaymentInfo()}");
        }

        if (orderState != OrderState.NotFound) {
            return false;
        }

        var paymentInfo = res.getPaymentInfo();
        //trade_status为1
        //保存
        PayResult payResult = new PayResult();
        payResult.setState_time(paymentInfo.getUpdate_time());
        payResult.setOrder_no(paymentInfo.getOrder_no());
        payResult.setPayinfo_id(paymentInfo.getId());
        payResult.setState(0);
        payResult.setState_finished(1);
        payResult.setAct_reason(OrderState.Payed.getValue());
        var sr = false;
        try {
            sr = payResultService.save(payResult);
        } catch (DuplicateKeyException e) {
            PayResult one1 = payResultService.lambdaQuery()
//                    .eq(PayResult::getPayinfo_id,paymentInfo.getId())
                    .eq(PayResult::getOrder_no, payResult.getOrder_no()).one();
            Objects.nonNull(one1);
            if (payResultState(one1)==OrderState.Payed) {
                if(one1.getPayinfo_id()==paymentInfo.getId()) {
                    log.info(STR. "重复保存订单 \{ payResult.getOrder_no() }" );
                }else{//其他人付款了
                    return false;
                }
            }else if(payResultState(one1)==OrderState.Cancel){
                //如果订单为Cancel状态,那么我们可以判断两个可能,如下:
                //1. 假如try_close函数选择取消订单,那么当前应该退款
                //2 如果选择支付,当前应该不进行操作

                if(one1.getState_time().isAfter(paymentInfo.getUpdate_time())){
                    //当前会被try_close函数考虑到,且可能被选择为支付
                    //所以,我们不需要任何操作,try_close函数会掌管好退款
                    return true;
                }else{
                    return false;
                }
            } else {
                return false;
            }
            sr = true;
        }
        if (!sr) {
            throw new RuntimeException(STR. "保存订单失败 \{ payResult.getOrder_no() }" );
        }
        log.info(STR."保存pay result成功");
        return true;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static final class CloseOrderParam {
        private String order_no;
        private String close_reason = "";
        private Boolean force_close; //是否把cancel状态变成为canceled
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static final class CloseOrderRes {
        private String order_state;
        private String close_reason = "";
    }

    @Autowired
    private StreamBridge streamBridge;

    //关闭订单,返回操作之后的状态
    @PostMapping("/close_order")
    public OrRes<CloseOrderRes> close_order(@RequestBody CloseOrderParam closeOrderParam) {
        var orRes = try_close(closeOrderParam.order_no, closeOrderParam.close_reason);
//        if (closeOrderParam.force_close && orRes.res().order_state.equals(OrderState.Cancel.getValue())) {
//            log.info(STR. "强制取消订单 \{ closeOrderParam.order_no }" );
//            var ok=payResultService.lambdaUpdate().eq(PayResult::getOrder_no, closeOrderParam.order_no)
//                    .and(self -> {
//                                self.eq(PayResult::getState_finished, 0);
//                                self.or();
//                                self.eq(PayResult::getState_finished, 1);
//                            }
//                    )
//                    .eq(PayResult::getState, 2)
//                    .set(PayResult::getState_finished, 1)
//                    .update();
//            if(ok) {
//                streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(closeOrderParam.order_no, "all"));
//                orRes.res().setOrder_state(OrderState.Canceled.getValue());
//            }
//        }
        return orRes;
    }

    //返回操作之后的状态
    //尝试关闭,关闭状态可能是cancel或者canceled
    @NotNull
    private OrRes<CloseOrderRes> try_close(String order_no, String act_reason) {
        log.info(STR."\{order_no} 取消订单开始");
        //前幂等
        PayResult one = payResultService.lambdaQuery().eq(PayResult::getOrder_no, order_no).one();
        if (one != null) {
            if (payResultState(one) == OrderState.Cancel) {
                log.info("继续关闭订单流程");
            } else {
                return OrRes.ok(new CloseOrderRes(payResultState(one).getValue(), one.getAct_reason()));
            }
        } else {
            PayResult payResult = new PayResult();
            //设置取消时间
            payResult.setState_time(LocalDateTime.now());
            payResult.setPayinfo_id(0l);
            payResult.setOrder_no(order_no);
            payResult.setState(2);
            payResult.setState_finished(0);
            payResult.setAct_reason(act_reason);
            try {
                //设置终止订单状态,这样已经支付的服务会在最终插入时注意到交易被终止,则进入退款流程
                payResultService.save(payResult);
                //主键自增的,保存完毕后会返回到payResult中
                one=payResult;
            } catch (DuplicateKeyException e) {
                PayResult one1 = payResultService.lambdaQuery().eq(PayResult::getOrder_no, order_no).one();
                Objects.nonNull(one1);
                if (payResultState(one1) == OrderState.Cancel) {
                    log.info("继续关闭订单流程");
                } else {
                    return OrRes.ok(new CloseOrderRes(payResultState(one1).getValue(), one1.getAct_reason()));
                }
                one = one1;
            }
        }

        //如果到达此处,那么三方支付将不存在新的支付请求.应为此时状态为cancel
        /// TODO: 11/20/23  此处有问题.我们在此处无法确保所有三方支付服务的状态,但当这些支付服务注意到cancel时,会有相应的回退逻辑
        //此时状态为cancel,注意之后的流程可以转换到canceled,同时也可以转换到payed.
        //我们只尝试终止这些状态是0的订单.其他状态的订单,例如那些已经支付成功的,最后会被退款
        var order_list = paymentInfoService.lambdaQuery()
                .eq(PaymentInfo::getOrder_no, order_no)
//                .eq(PaymentInfo::getTrade_state, "0")
                .list();
        //成功条件1.还没有人正在进行三方支付,所以可以安全地关闭
        if (order_list.isEmpty()) {
            if (payResultService.lambdaUpdate().eq(PayResult::getOrder_no, order_no)
                    .eq(PayResult::getState, 2)
                    .set(PayResult::getState_finished, 1)
                    .update()) {
                return OrRes.ok(new CloseOrderRes(OrderState.Canceled.getValue(), one.getAct_reason()));
            }
            PayResult one1 = payResultService.lambdaQuery().eq(PayResult::getOrder_no, order_no).one();
            return OrRes.ok(new CloseOrderRes(payResultState(one1).getValue(), one1.getAct_reason()));
        }

        //查询订单状态
        order_list.stream().filter(i->i.getTrade_state().equals("0")).forEach(os->{
            var orderState = basePayServiceComposite.poll_trade(os);
            os.setTrade_state(pi_order_state_map.get(orderState));
        });

        List<PaymentInfo> payed_list=new ArrayList<>();
        var unknown_list=new ArrayList<PaymentInfo>();
        order_list.forEach(i->{
            OrderState orderState = get_3p_order_state(i);
            if(orderState ==OrderState.Payed){
                payed_list.add(i);
            } else if (orderState ==OrderState.Created||orderState==OrderState.NotFound) {
                unknown_list.add(i);
            } else{
                ;
            }
        });
        // TODO: 11/29/23 找到付款订单 ,比较它的付款时间与取消时间
        var payed_precedence=false;
        if(!payed_list.isEmpty()){
            //决定第一个付款的
            payed_list.sort(Comparator.comparing(PaymentInfo::getUpdate_time));
            if(payed_list.get(0).getUpdate_time().isBefore(one.getState_time())){
                payed_precedence=true;
            }
            //这里能直接发送退款消息,因为虽然还未真正插入当前选择的付款,但是已经确定会插入这个支付,且其他地方的插入,例如支付回调时,不会成功
            payed_list.subList(1,payed_list.size()).forEach(paymentInfo->{
                log.info(STR. "发送退款消息 \{ order_no } \{paymentInfo.getPayment_type()}" );
                streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(paymentInfo.getOrder_no(), paymentInfo.getPayment_type()));
            });
        }
        unknown_list.stream().forEach(o -> {
            basePayServiceComposite.close_order(o);
        });

        if (payed_precedence){//有付款成功
            var ok = payResultService.lambdaUpdate().eq(PayResult::getOrder_no, order_no)
                    .eq(PayResult::getState, 2)
                    .eq(PayResult::getState_finished,0)
                    .set(PayResult::getState, 0)
                    .set(PayResult::getState_finished, 1)
                    .set(PayResult::getPayinfo_id,payed_list.get(0).getId())
                    .set(PayResult::getState_time,payed_list.get(0).getUpdate_time())
                    .update();
            if(ok){
                //fixme 要是这里失败了怎么办,难道需要事务消息吗?还是不管它,等待超时退款清理?
//                payed_list.subList(1,payed_list.size()).forEach(paymentInfo->{
//                    log.info(STR. "发送退款消息 \{ order_no } \{paymentInfo.getPayment_type()}" );
//                    streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(paymentInfo.getOrder_no(), paymentInfo.getPayment_type()));
//                });
                log.info(STR."\{order_no} 取消订单,判断为支付.成功结束");
                return OrRes.ok(new CloseOrderRes(OrderState.Payed.getValue(), OrderState.Payed.getValue()));
            }else{
                //todo 这里的失败是由于当前是过时的操作,或者相同的插入,因此直接返回就好  done
                PayResult one1 = payResultService.lambdaQuery().eq(PayResult::getOrder_no, order_no)
//                        .eq(PayResult::getState, 0)
//                        .eq(PayResult::getState_finished,1)
                        .one();
                // one1可能为null
//                paymentInfoService.lambdaQuery().eq(PaymentInfo::getOrder_no, order_no)
//                        .ne(PaymentInfo::getId, one1!=null?one1.getPayinfo_id():0).list()
//                        .forEach(paymentInfo->{
//                            log.info(STR. "发送退款消息 \{ order_no } \{paymentInfo.getPayment_type()}" );
//                            streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(paymentInfo.getOrder_no(), paymentInfo.getPayment_type()));
//                        });
                return OrRes.ok(new CloseOrderRes(payResultState(one1).getValue(), one1.getAct_reason()));
            }
        }else{//没有付款成功
            payed_list.forEach(paymentInfo->{
                log.info(STR. "发送退款消息 \{ order_no } \{paymentInfo.getPayment_type()}" );
                streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(paymentInfo.getOrder_no(), paymentInfo.getPayment_type()));
            });
            if (payResultService.lambdaUpdate().eq(PayResult::getOrder_no, order_no)
                    .eq(PayResult::getState, 2)
                    .set(PayResult::getState_finished, 1)
                    .update()) {
                return OrRes.ok(new CloseOrderRes(OrderState.Canceled.getValue(), one.getAct_reason()));
            }
            PayResult one1 = payResultService.lambdaQuery().eq(PayResult::getOrder_no, order_no).one();
            return OrRes.ok(new CloseOrderRes(payResultState(one1).getValue(), one1.getAct_reason()));
        }
    }

    //map不可修改
    private static final Map<String, Integer> order_state_map = Map.of(
            OrderState.Created.getValue(), 0,
//            OrderState.NotFound.getValue(), 0,
            OrderState.Payed.getValue(), 1,
            OrderState.Cancel.getValue(), 2,
            OrderState.Timeout.getValue(), 3
    );

    public static OrderState payResultState(PayResult payResult) {
        return switch (payResult) {
            case null -> OrderState.NotFound;
            case PayResult p when p.getState() == 2 && p.getState_finished() == 0 -> OrderState.Cancel;
            case PayResult p when p.getState() == 1 && p.getState_finished() == 1 -> OrderState.Refund;
            case PayResult p when p.getState() == 0 && p.getState_finished() == 1 -> OrderState.Payed;
            case PayResult p when p.getState() == 2 && p.getState_finished() == 1 -> OrderState.Canceled;
            default -> throw new RuntimeException("未实现");
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static final class TradeStateParam {
        private String order_no;
    }

    //返回状态
    @PostMapping("/poll_trade_state")
    public OrRes<String> poll_trade_state(@RequestBody TradeStateParam tradeStateParam) {
        //首先得到目前状态,如果状态是0,则需要去各个支付服务poll状态

        PayResult one = payResultService.lambdaQuery().eq(PayResult::getOrder_no, tradeStateParam.getOrder_no()).one();
        return OrRes.ok(payResultState(one).getValue());
        //因为当前的状态是null,可能后续流程正在处理中.我们好像应该直接返回
        // 但存在这种情况,即支付后的通知没有收到,那么流程就无法进行
        // 在这种情况下,我们进行查询,同时希望下一个状态是payed
/*        if (one == null) {
            List<PaymentInfo> list = paymentInfoService.lambdaQuery().eq(PaymentInfo::getOrder_no, tradeStateParam.getOrder_no())
                    .orderByDesc(PaymentInfo::getCreate_time)
                    .list();
            if (list.isEmpty()) {
                return OrRes.ok(OrderState.NotFound.getValue());
            }

            //查询支付状态
            var c = list.stream().map(basePayServiceComposite::poll_trade).collect(Collectors.toSet());

            if (c.contains(OrderState.Payed)) {
                //todo 决定哪一个作为付款,其他都退款
                List<PaymentInfo> list1 = paymentInfoService.lambdaQuery().eq(PaymentInfo::getOrder_no, tradeStateParam.getOrder_no())
                        .eq(PaymentInfo::getTrade_state,"1")
                        .orderByDesc(PaymentInfo::getCreate_time)
                        .list();
                assert !list1.isEmpty();
                var choose=list1.get(0);
                PayResult payResult = new PayResult();
                payResult.setState_time(choose.getUpdate_time());
                payResult.setOrder_no(choose.getOrder_no());
                payResult.setPayinfo_id(choose.getId());
                payResult.setState(0);
                payResult.setState_finished(1);
                payResult.setAct_reason(OrderState.Payed.getValue());
                try {
                    payResultService.save(payResult);
                } catch (DuplicateKeyException e) {
                    // TODO: 11/20/23 如果状态是cancel,需要判断cancel状态与payed状态的时间来决定是否从cancel转换到payed
                    PayResult one2 = payResultService.lambdaQuery().eq(PayResult::getOrder_no, tradeStateParam.getOrder_no()).one();
                    log.warn("保存订单失败,返回");
                    return OrRes.ok(payResultState(one2).getValue());
                }
                return OrRes.ok(OrderState.Payed.getValue());
            } else {//不处理其他状态,因为暂时不存在其他状态会卡住
                throw new RuntimeException("暂未实现此状态");
            }

        } else {//返回当前状态
            return OrRes.ok(payResultState(one).getValue());
        }*/
    }

    //必须在已经超时后调用
    @PostMapping("/clean_up_unreached_order")
    public OrRes<?> clean_up_unreached_order(@RequestBody String order_no){
        PayResult one1 = payResultService.lambdaQuery().eq(PayResult::getOrder_no, order_no)
                .one();
        //因为未支付的订单会超时,所以只需要解决不可达的已支付的订单,我们采取退款操作
        paymentInfoService.lambdaQuery().eq(PaymentInfo::getOrder_no, order_no)
                .ne(PaymentInfo::getId, one1!=null?one1.getPayinfo_id():0)
                .eq(PaymentInfo::getTrade_state,"0").list()
                .forEach(paymentInfo->{
                    log.info(STR. "发送退款消息.订单号: \{ order_no } 付款类型\{paymentInfo.getPayment_type()}" );
                    streamBridge.send("pay_refund-out-0", new PayRefundConsumer.RefundMsgParam(paymentInfo.getOrder_no(), paymentInfo.getPayment_type()));
                });
        return OrRes.ok();
    }

}
